function LineTracker(){
    this.init$LineTracker();
}

LineTracker.prototype = {
    _lineDelimiterRegex: /\r?\n|\r/g,
    _endOffset: 0,
    _lineBreaks: null,
    _lineIndexCache: null,
    init$LineTracker: function (){
        this._lineBreaks = [];
        this._lineIndexCache = {};
    },
    // 不考虑offset恰好在delimiter中间或者插入的\n与前面一个\r结合的变态情况.
    update$LineTracker: function (offset, length, text){
        var deltaOffset = text.length - length;
        var index;
        var delCount;
        var addCount;
        if (offset === 0 && length === this._endOffset) {
            index = 0;
            delCount = this._lineBreaks.length;
            this._parseLineBreaks(text, offset, this._lineBreaks = []);
            addCount = this._lineBreaks.length;
        } else {
            var lineBreaks = this._lineBreaks;
            index = this.getLineIndex(offset);
            delCount = this.getLineIndex(offset + length) - index;
            var array = [];
            this._parseLineBreaks(text, offset, array);
            addCount = array.length;
            if (delCount > 0 || addCount > 0) {
                lineBreaks = lineBreaks.slice(0, index).concat(
                    array, lineBreaks.slice(index + delCount));
            }
            var end = lineBreaks.length;
            for (var i=index+addCount; i<end; i++) {
                lineBreaks[i][0] += deltaOffset;
            }
            this._lineBreaks = lineBreaks;
        }
        this._lineIndexCache = {};
        this._endOffset += deltaOffset;
        return {
            startIndex: index,
            endIndexOld: index + delCount,
            endIndex: index + addCount
        };
    },
    update: function (offset, length, text){
        this.update$LineTracker(offset, length, text);
    },
    _parseLineBreaks: function (text, baseOffset, buff){
        var match;
        var regex = this._lineDelimiterRegex;
        regex.lastIndex = 0;
        while (match = regex.exec(text)) {
            buff.push([baseOffset + regex.lastIndex, match[0]]);
        }
    },
    getLength: function (){
        return this._endOffset;
    },
    getLineCount: function (){
        return this._lineBreaks.length + 1;
    },
    getLineIndex: function (offset){
        if (offset === 0 || this._lineBreaks.length === 0) {
            return 0;
        }
        if (offset === this._endOffset) {
            return this._lineBreaks.length;
        }
        if (this._lineIndexCache[offset] != null) {
            return this._lineIndexCache[offset];
        }
        var lineBreaks = this._lineBreaks;
        var left = 0;
        var right = lineBreaks.length;
        var lastIndex = right - 1;
        while (left < right) {
            var middle = (left + right) >> 1;
            var lineBreak = lineBreaks[middle];
            var offset1 = lineBreak[0];
            if (offset >= offset1) {
                if (middle === lastIndex) {
                    return lastIndex + 1;
                }
                if (offset < lineBreaks[middle + 1][0]) {
                    return (this._lineIndexCache[offset1] = middle + 1);
                }
                left = middle + 1;
            } else {
                if (middle === 0) {
                    return 0;
                }
                right = middle;
            }
        }
        return left + 1;
    },
    getLineStartOffset: function (index){
        return index === 0 ? 0 : this._lineBreaks[index - 1][0];
    },
    getLineEndOffset: function (index){
        var lineBreaks = this._lineBreaks;
        if (index === lineBreaks.length) {
            return this._endOffset;
        }
        var lineBreak = lineBreaks[index];
        return lineBreak[0] - lineBreak[1].length;
    },
    getLineLength: function (index){
        return this.getLineEndOffset(index) - this.getLineStartOffset(index);
    },
    getLineDelimiter: function (index){
        var lineBreaks = this._lineBreaks;
        if (index === lineBreaks.length) {
            return '';
        }
        return lineBreaks[index][1];
    }
};
